Table of Contents
- Introduction
- Pipes with magrittr
- nycflights13
- dplyr basics
- Filter rows with filter()
- Missing values
- Arrange rows with arrange()
- Select columns with select()
Introduction
Visualization is an important tool for insight generation, but it is rare that you get the data in exactly the right form you need. Often you’ll need to create some new variables or summaries, or maybe you just want to rename the variables or reorder the observations in order to make the data a little easier to work with. You’ll learn how to do all that in this notebook and next one, which will teach you how to transform your data using the dplyr package and a new dataset on flights departing New York City in 2013 (from nycflights13 package).
Pipes with magrittr 
The pipe, %>%, comes from the magrittr package. Packages in the tidyverse load %>% for you automatically, so you don’t usually load magrittr explicitly.
Pipe was designed to decrease development time and to improve readability of the code. magrittr provides a “pipe”-like operator, %>%, with which you may pipe a value forward into an expression or function call; something along the lines of x %>% f, rather than f(x).
Example:
mpg %>% nrow
[1] 234
Equivalent to nrow(mpg). You might ask why bother to learn a new syntax when the conventional f(x) works just fine; the answer is that pipes become very handy as the number of operations grow. For instance we will see later in this notebook that we can pipe multiple steps together, rather than saving them in temporary variables and reusing them. E.g., x %>% f %>% g %>% h rather than h(g(f(x))).
When using a pipe we can pass the primary object to the function by a pipe and define the secondary function parameters inside as we would normally do. The following code is equivalent to matrix(rnorm(10), ncol = 2):
10 %>%
rnorm %>%
matrix(ncol = 2)
[,1] [,2]
[1,] -0.5267131 0.5276985
[2,] -1.0095321 -2.1470418
[3,] 0.7301232 0.2334405
[4,] -0.1132265 -1.0269735
[5,] 1.1350027 1.4097968
Let’s make more random numbers and pipe a plot function at the end:
100 %>%
rnorm %>%
matrix(ncol = 2) %>%
plot

nycflights13
To explore the basic data manipulation verbs of dplyr, we’ll use nycflights13::flights. This data frame contains all 336,776 flights that departed from New York City in 2013. The data comes from the US Bureau of Transportation Statistics, and is documented in ?flights.
flights
dplyr basics
dplyr is a grammar of data transformation, providing a consistent set of verbs that help you solve the most common data transformation challenges:
filter() picks cases based on their values.
arrange() changes the ordering of the rows.
select() picks variables based on their names.
mutate() adds new variables that are functions of existing variables
summarize() reduces multiple values down to a single summary.
These six functions provide the verbs for a language of data transformation These all combine naturally with group_by() which allows you to perform any operation “by group”.
All verbs work similarly:
- The first argument is a data frame.
- The subsequent arguments describe what to do with the data frame, using the variable names (without quotes).
- The result is a new data frame.
Filter rows with filter()
filter() allows you to subset observations based on their values. The first argument is the name of the data frame. The second and subsequent arguments are the expressions that filter the data frame. For example, we can select all flights on January 1st with:
filter(flights, month == 1, day == 1)
dplyr functions never modify their inputs, so if you want to save the result, you’ll need to use the assignment operator, <-:
jan1 <- filter(flights, month == 1, day == 1)
Comparisons
R provides the standard suite: >, >=, <, <=, != (not equal), and == (equal) for performing comparisons. All of these can be used inside filter(). For instance the following code will filter out the flights without any departure delay:
flights %>%
filter(dep_delay <= 0)
Logical operators
We can provide multiple comparisons to filter(). For this you’ll need to use Boolean operators: & is “and”, | is “or”, and ! is “not”. Figure below shows a complete set of Boolean operations.
The following code finds all flights that departed in November or December:
flights %>%
filter(month == 11 | month == 12)
Alternatively we could use the following syntax:
filter(flights, month %in% c(11, 12))
Whenever you start using complicated, multi-part expressions in filter(), consider making them explicit variables instead. That makes it much easier to check your work. You’ll learn how to create new variables shortly.
Missing values
One important feature of R that can make comparison tricky is missing value, or NA (“Not Available”). NA represents an unknown value so missing values are “contagious”: almost any operation involving an unknown value will also be unknown:
NA > 5
[1] NA
10 == NA
[1] NA
NA + 10
[1] NA
NA / 2
[1] NA
NA == NA
[1] NA
It’s easiest to understand why this is true with a bit more context:
# Let x be Mary's age. We don't know how old she is.
x <- NA
# Let y be John's age. We don't know how old he is.
y <- NA
# Are John and Mary the same age?
x == y
[1] NA
We don’t know!
Use is.na() to determine if a value is missing
is.na(x)
[1] TRUE
filter() only includes rows where the condition is TRUE; it excludes both FALSE and NA values. If you want to preserve missing values, ask for them explicitly:
(df <- tibble(x = c(1, NA, 3)))
filter(df, x > 1)
filter(df, is.na(x) | x > 1)
Arrange rows with arrange()
arrange() works similarly to filter() except that instead of selecting rows, it does what it claims it does: changes their order. It takes a data frame and a set of column names (or more complicated expressions) to order by. If you provide more than one column name, each additional column will be used to break ties in the values of preceding columns:
arrange(flights, year, month, day)
Use desc() to re-order by a column in descending order:
arrange(flights, desc(dep_delay))
Missing values are always sorted at the end:
df <- tibble(x = c(5, 6, 2, NA))
arrange(df, x)
arrange(df, desc(x))
Select columns with select()
It’s not uncommon to get datasets with hundreds or even thousands of variables. In this case, the first challenge is often narrowing in on the variables you’re actually interested in. select() allows you to do that:
# Select columns by name
select(flights, year, month, day)
# Select columns between year and day (inclusive)
select(flights, year:day)
# Select all columns except those from year to day (inclusive)
select(flights, -(year:day))
There are a number of helper functions you can use within select():
starts_with("abc"): matches names that begin with “abc”.
ends_with("xyz"): matches names that end with “xyz”.
contains("ijk"): matches names that contain “ijk”.
num_range("x", 1:3): matches x1, x2 and x3.
everything(): useful for bringing a few columns to the beginning of the dataframe and still want to keep everything else at the end.
See ?select for more details.
select() can be used to rename variables (e.g., select(flights, y = year)), but it’s rarely useful because it drops all of the variables not explicitly mentioned. Instead, use rename(), which is a variant of select() that keeps all the variables that aren’t explicitly mentioned:
rename(flights, y = year, tail_num = tailnum)
everything() example
select(flights, time_hour, aritime = air_time, everything())
LS0tCnRpdGxlOiAiRGF0YSBUcmFuc2Zvcm1hdGlvbiIKc3VidGl0bGU6ICJQYXJ0IDEiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCiMjIyBUYWJsZSBvZiBDb250ZW50cwoqIEludHJvZHVjdGlvbgoqIFBpcGVzIHdpdGggbWFncml0dHIKKiBueWNmbGlnaHRzMTMKKiBkcGx5ciBiYXNpY3MKKiBGaWx0ZXIgcm93cyB3aXRoIGZpbHRlcigpCiogTWlzc2luZyB2YWx1ZXMKKiBBcnJhbmdlIHJvd3Mgd2l0aCBhcnJhbmdlKCkKKiBTZWxlY3QgY29sdW1ucyB3aXRoIHNlbGVjdCgpCgpgYGB7ciBlY2hvPUZBTFNFfQojIGxvYWRpbmcgbGlicmFyaWVzCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGx1YnJpZGF0ZSkKbGlicmFyeShueWNmbGlnaHRzMTMpCmxpYnJhcnkocGxvdGx5KQoKIyBtb2RpZnlpbmcgY2hhcnQgc2l6ZQpvcHRpb25zKHJlcHIucGxvdC53aWR0aD01LCByZXByLnBsb3QuaGVpZ2h0PTMpCmBgYAoKIyMgSW50cm9kdWN0aW9uClZpc3VhbGl6YXRpb24gaXMgYW4gaW1wb3J0YW50IHRvb2wgZm9yIGluc2lnaHQgZ2VuZXJhdGlvbiwgYnV0IGl0IGlzIHJhcmUgdGhhdCB5b3UgZ2V0IHRoZSBkYXRhIGluIGV4YWN0bHkgdGhlIHJpZ2h0IGZvcm0geW91IG5lZWQuIE9mdGVuIHlvdSdsbCBuZWVkIHRvIGNyZWF0ZSBzb21lIG5ldyB2YXJpYWJsZXMgb3Igc3VtbWFyaWVzLCBvciBtYXliZSB5b3UganVzdCB3YW50IHRvIHJlbmFtZSB0aGUgdmFyaWFibGVzIG9yIHJlb3JkZXIgdGhlIG9ic2VydmF0aW9ucyBpbiBvcmRlciB0byBtYWtlIHRoZSBkYXRhIGEgbGl0dGxlIGVhc2llciB0byB3b3JrIHdpdGguIFlvdSdsbCBsZWFybiBob3cgdG8gZG8gYWxsIHRoYXQgaW4gdGhpcyBub3RlYm9vayBhbmQgbmV4dCBvbmUsIHdoaWNoIHdpbGwgdGVhY2ggeW91IGhvdyB0byB0cmFuc2Zvcm0geW91ciBkYXRhIHVzaW5nIHRoZSAqKmRwbHlyKiogcGFja2FnZSBhbmQgYSBuZXcgZGF0YXNldCBvbiBmbGlnaHRzIGRlcGFydGluZyBOZXcgWW9yayBDaXR5IGluIDIwMTMgKGZyb20gbnljZmxpZ2h0czEzIHBhY2thZ2UpLgoKLS0tCgoKCiMjIFBpcGVzIHdpdGggbWFncml0dHIgICFbXSguLi9wbmcvbWFncml0dHIucG5nKXt3aWR0aD0xMDB9ClRoZSBwaXBlLCBgJT4lYCwgY29tZXMgZnJvbSB0aGUgKiptYWdyaXR0cioqIHBhY2thZ2UuIFBhY2thZ2VzIGluIHRoZSB0aWR5dmVyc2UgbG9hZCBgJT4lYCBmb3IgeW91IGF1dG9tYXRpY2FsbHksIHNvIHlvdSBkb24ndCB1c3VhbGx5IGxvYWQgbWFncml0dHIgZXhwbGljaXRseS4KClBpcGUgd2FzIGRlc2lnbmVkIHRvIGRlY3JlYXNlIGRldmVsb3BtZW50IHRpbWUgYW5kIHRvIGltcHJvdmUgcmVhZGFiaWxpdHkgb2YgdGhlIGNvZGUuIG1hZ3JpdHRyIHByb3ZpZGVzIGEg4oCccGlwZeKAnS1saWtlIG9wZXJhdG9yLCBgJT4lYCwgd2l0aCB3aGljaCB5b3UgbWF5IHBpcGUgYSB2YWx1ZSBmb3J3YXJkIGludG8gYW4gZXhwcmVzc2lvbiBvciBmdW5jdGlvbiBjYWxsOyBzb21ldGhpbmcgYWxvbmcgdGhlIGxpbmVzIG9mIGB4ICU+JSBmYCwgcmF0aGVyIHRoYW4gYGYoeClgLiAKCkV4YW1wbGU6CgpgYGB7cn0KbXBnICU+JSBucm93CmBgYAoKRXF1aXZhbGVudCB0byBgbnJvdyhtcGcpYC4gWW91IG1pZ2h0IGFzayB3aHkgYm90aGVyIHRvIGxlYXJuIGEgbmV3IHN5bnRheCB3aGVuIHRoZSBjb252ZW50aW9uYWwgYGYoeClgIHdvcmtzIGp1c3QgZmluZTsgdGhlIGFuc3dlciBpcyB0aGF0IHBpcGVzIGJlY29tZSB2ZXJ5IGhhbmR5IGFzIHRoZSBudW1iZXIgb2Ygb3BlcmF0aW9ucyBncm93LiBGb3IgaW5zdGFuY2Ugd2Ugd2lsbCBzZWUgbGF0ZXIgaW4gdGhpcyBub3RlYm9vayB0aGF0IHdlIGNhbiBwaXBlIG11bHRpcGxlIHN0ZXBzIHRvZ2V0aGVyLCByYXRoZXIgdGhhbiBzYXZpbmcgdGhlbSBpbiB0ZW1wb3JhcnkgdmFyaWFibGVzIGFuZCByZXVzaW5nIHRoZW0uIEUuZy4sIGB4ICU+JSBmICU+JSBnICU+JSBoYCByYXRoZXIgdGhhbiBgaChnKGYoeCkpKWAuCgpXaGVuIHVzaW5nIGEgcGlwZSB3ZSBjYW4gcGFzcyB0aGUgcHJpbWFyeSBvYmplY3QgdG8gdGhlIGZ1bmN0aW9uIGJ5IGEgcGlwZSBhbmQgZGVmaW5lIHRoZSBzZWNvbmRhcnkgZnVuY3Rpb24gcGFyYW1ldGVycyBpbnNpZGUgYXMgd2Ugd291bGQgbm9ybWFsbHkgZG8uIFRoZSBmb2xsb3dpbmcgY29kZSBpcyBlcXVpdmFsZW50IHRvIGBtYXRyaXgocm5vcm0oMTApLCBuY29sID0gMilgOgoKYGBge3J9CjEwICU+JQogIHJub3JtICU+JQogIG1hdHJpeChuY29sID0gMikKYGBgCgpMZXQncyBtYWtlIG1vcmUgcmFuZG9tIG51bWJlcnMgYW5kIHBpcGUgYSBwbG90IGZ1bmN0aW9uIGF0IHRoZSBlbmQ6CgpgYGB7cn0KMTAwICU+JQogIHJub3JtICU+JQogIG1hdHJpeChuY29sID0gMikgJT4lCiAgcGxvdApgYGAKCgotLS0KCiMjIG55Y2ZsaWdodHMxMwpUbyBleHBsb3JlIHRoZSBiYXNpYyBkYXRhIG1hbmlwdWxhdGlvbiB2ZXJicyBvZiBkcGx5ciwgd2UnbGwgdXNlIGBueWNmbGlnaHRzMTM6OmZsaWdodHNgLiBUaGlzIGRhdGEgZnJhbWUgY29udGFpbnMgYWxsIDMzNiw3NzYgZmxpZ2h0cyB0aGF0IGRlcGFydGVkIGZyb20gTmV3IFlvcmsgQ2l0eSBpbiAyMDEzLiBUaGUgZGF0YSBjb21lcyBmcm9tIHRoZSBVUyBCdXJlYXUgb2YgVHJhbnNwb3J0YXRpb24gU3RhdGlzdGljcywgYW5kIGlzIGRvY3VtZW50ZWQgaW4gYD9mbGlnaHRzYC4KCmBgYHtyfQpmbGlnaHRzCmBgYAoKLS0tCgojIyBkcGx5ciBiYXNpY3MKKipkcGx5cioqIGlzIGEgZ3JhbW1hciBvZiBkYXRhIHRyYW5zZm9ybWF0aW9uLCBwcm92aWRpbmcgYSBjb25zaXN0ZW50IHNldCBvZiB2ZXJicyB0aGF0IGhlbHAgeW91IHNvbHZlIHRoZSBtb3N0IGNvbW1vbiBkYXRhIHRyYW5zZm9ybWF0aW9uIGNoYWxsZW5nZXM6CgoqIGBmaWx0ZXIoKWAgcGlja3MgY2FzZXMgYmFzZWQgb24gdGhlaXIgdmFsdWVzLgoqIGBhcnJhbmdlKClgIGNoYW5nZXMgdGhlIG9yZGVyaW5nIG9mIHRoZSByb3dzLgoqIGBzZWxlY3QoKWAgcGlja3MgdmFyaWFibGVzIGJhc2VkIG9uIHRoZWlyIG5hbWVzLgoqIGBtdXRhdGUoKWAgYWRkcyBuZXcgdmFyaWFibGVzIHRoYXQgYXJlIGZ1bmN0aW9ucyBvZiBleGlzdGluZyB2YXJpYWJsZXMKKiBgc3VtbWFyaXplKClgIHJlZHVjZXMgbXVsdGlwbGUgdmFsdWVzIGRvd24gdG8gYSBzaW5nbGUgc3VtbWFyeS4KClRoZXNlIHNpeCBmdW5jdGlvbnMgcHJvdmlkZSB0aGUgdmVyYnMgZm9yIGEgbGFuZ3VhZ2Ugb2YgZGF0YSB0cmFuc2Zvcm1hdGlvbiBUaGVzZSBhbGwgY29tYmluZSBuYXR1cmFsbHkgd2l0aCBgZ3JvdXBfYnkoKWAgd2hpY2ggYWxsb3dzIHlvdSB0byBwZXJmb3JtIGFueSBvcGVyYXRpb24gImJ5IGdyb3VwIi4KCkFsbCB2ZXJicyB3b3JrIHNpbWlsYXJseToKCjEuIFRoZSBmaXJzdCBhcmd1bWVudCBpcyBhIGRhdGEgZnJhbWUuCjIuIFRoZSBzdWJzZXF1ZW50IGFyZ3VtZW50cyBkZXNjcmliZSB3aGF0IHRvIGRvIHdpdGggdGhlIGRhdGEgZnJhbWUsIHVzaW5nIHRoZSB2YXJpYWJsZSBuYW1lcyAod2l0aG91dCBxdW90ZXMpLgozLiBUaGUgcmVzdWx0IGlzIGEgbmV3IGRhdGEgZnJhbWUuCgojIyBGaWx0ZXIgcm93cyB3aXRoIGBmaWx0ZXIoKWAKYGZpbHRlcigpYCBhbGxvd3MgeW91IHRvIHN1YnNldCBvYnNlcnZhdGlvbnMgYmFzZWQgb24gdGhlaXIgdmFsdWVzLiBUaGUgZmlyc3QgYXJndW1lbnQgaXMgdGhlIG5hbWUgb2YgdGhlIGRhdGEgZnJhbWUuIFRoZSBzZWNvbmQgYW5kIHN1YnNlcXVlbnQgYXJndW1lbnRzIGFyZSB0aGUgZXhwcmVzc2lvbnMgdGhhdCBmaWx0ZXIgdGhlIGRhdGEgZnJhbWUuIEZvciBleGFtcGxlLCB3ZSBjYW4gc2VsZWN0IGFsbCBmbGlnaHRzIG9uIEphbnVhcnkgMXN0IHdpdGg6CgpgYGB7cn0KZmlsdGVyKGZsaWdodHMsIG1vbnRoID09IDEsIGRheSA9PSAxKQpgYGAKCmRwbHlyIGZ1bmN0aW9ucyBuZXZlciBtb2RpZnkgdGhlaXIgaW5wdXRzLCBzbyBpZiB5b3Ugd2FudCB0byBzYXZlIHRoZSByZXN1bHQsIHlvdSdsbCBuZWVkIHRvIHVzZSB0aGUgYXNzaWdubWVudCBvcGVyYXRvciwgYDwtYDoKCmBgYHtyfQpqYW4xIDwtIGZpbHRlcihmbGlnaHRzLCBtb250aCA9PSAxLCBkYXkgPT0gMSkKYGBgCgojIyMgQ29tcGFyaXNvbnMKUiBwcm92aWRlcyB0aGUgc3RhbmRhcmQgc3VpdGU6IGA+YCwgYD49YCwgYDxgLCBgPD1gLCBgIT1gIChub3QgZXF1YWwpLCBhbmQgYD09YCAoZXF1YWwpIGZvciBwZXJmb3JtaW5nIGNvbXBhcmlzb25zLiBBbGwgb2YgdGhlc2UgY2FuIGJlIHVzZWQgaW5zaWRlIGBmaWx0ZXIoKWAuIEZvciBpbnN0YW5jZSB0aGUgZm9sbG93aW5nIGNvZGUgd2lsbCBmaWx0ZXIgb3V0IHRoZSBmbGlnaHRzIHdpdGhvdXQgYW55IGRlcGFydHVyZSBkZWxheToKCmBgYHtyfQpmbGlnaHRzICU+JSAKICBmaWx0ZXIoZGVwX2RlbGF5IDw9IDApCmBgYAoKIyMjIExvZ2ljYWwgb3BlcmF0b3JzCldlIGNhbiBwcm92aWRlIG11bHRpcGxlIGNvbXBhcmlzb25zIHRvIGBmaWx0ZXIoKWAuIEZvciB0aGlzIHlvdSdsbCBuZWVkIHRvIHVzZSBCb29sZWFuIG9wZXJhdG9yczogYCZgIGlzICJhbmQiLCBgfGAgaXMgIm9yIiwgYW5kIGAhYCBpcyAibm90Ii4gRmlndXJlIGJlbG93IHNob3dzIGEgY29tcGxldGUgc2V0IG9mIEJvb2xlYW4gb3BlcmF0aW9ucy4KCjxjZW50ZXI+ICFbXSguLi9wbmcvbG9naWNhbC5wbmcpe3dpZHRoPTQwMH0gPC9jZW50ZXI+CgpUaGUgZm9sbG93aW5nIGNvZGUgZmluZHMgYWxsIGZsaWdodHMgdGhhdCBkZXBhcnRlZCBpbiBOb3ZlbWJlciAqKm9yKiogRGVjZW1iZXI6CgpgYGB7cn0KZmxpZ2h0cyAlPiUKICBmaWx0ZXIobW9udGggPT0gMTEgfCBtb250aCA9PSAxMikKYGBgCgpBbHRlcm5hdGl2ZWx5IHdlIGNvdWxkIHVzZSB0aGUgZm9sbG93aW5nIHN5bnRheDoKCj4gYGZpbHRlcihmbGlnaHRzLCBtb250aCAlaW4lIGMoMTEsIDEyKSlgCgpXaGVuZXZlciB5b3Ugc3RhcnQgdXNpbmcgY29tcGxpY2F0ZWQsIG11bHRpLXBhcnQgZXhwcmVzc2lvbnMgaW4gYGZpbHRlcigpYCwgY29uc2lkZXIgbWFraW5nIHRoZW0gZXhwbGljaXQgdmFyaWFibGVzIGluc3RlYWQuIFRoYXQgbWFrZXMgaXQgbXVjaCBlYXNpZXIgdG8gY2hlY2sgeW91ciB3b3JrLiBZb3UnbGwgbGVhcm4gaG93IHRvIGNyZWF0ZSBuZXcgdmFyaWFibGVzIHNob3J0bHkuCgojIyMgTWlzc2luZyB2YWx1ZXMKT25lIGltcG9ydGFudCBmZWF0dXJlIG9mIFIgdGhhdCBjYW4gbWFrZSBjb21wYXJpc29uIHRyaWNreSBpcyBtaXNzaW5nIHZhbHVlLCBvciBgTkFgICgiTm90IEF2YWlsYWJsZSIpLiBgTkFgIHJlcHJlc2VudHMgYW4gdW5rbm93biB2YWx1ZSBzbyBtaXNzaW5nIHZhbHVlcyBhcmUgImNvbnRhZ2lvdXMiOiBhbG1vc3QgYW55IG9wZXJhdGlvbiBpbnZvbHZpbmcgYW4gdW5rbm93biB2YWx1ZSB3aWxsIGFsc28gYmUgdW5rbm93bjoKCmBgYHtyfQpOQSA+IDUKYGBgCgpgYGB7cn0KMTAgPT0gTkEKYGBgCgpgYGB7cn0KTkEgKyAxMApgYGAKCmBgYHtyfQpOQSAvIDIKYGBgCgpgYGB7cn0KTkEgPT0gTkEKYGBgCgpJdCdzIGVhc2llc3QgdG8gdW5kZXJzdGFuZCB3aHkgdGhpcyBpcyB0cnVlIHdpdGggYSBiaXQgbW9yZSBjb250ZXh0OgoKYGBge3J9CiMgTGV0IHggYmUgTWFyeSdzIGFnZS4gV2UgZG9uJ3Qga25vdyBob3cgb2xkIHNoZSBpcy4KeCA8LSBOQQoKIyBMZXQgeSBiZSBKb2huJ3MgYWdlLiBXZSBkb24ndCBrbm93IGhvdyBvbGQgaGUgaXMuCnkgPC0gTkEKCiMgQXJlIEpvaG4gYW5kIE1hcnkgdGhlIHNhbWUgYWdlPwp4ID09IHkKYGBgCgpXZSBkb24ndCBrbm93IQoKVXNlIGBpcy5uYSgpYCB0byBkZXRlcm1pbmUgaWYgYSB2YWx1ZSBpcyBtaXNzaW5nCgpgYGB7cn0KaXMubmEoeCkKYGBgCgpgZmlsdGVyKClgIG9ubHkgaW5jbHVkZXMgcm93cyB3aGVyZSB0aGUgY29uZGl0aW9uIGlzIGBUUlVFYDsgaXQgZXhjbHVkZXMgYm90aCBgRkFMU0VgIGFuZCBgTkFgIHZhbHVlcy4gSWYgeW91IHdhbnQgdG8gcHJlc2VydmUgbWlzc2luZyB2YWx1ZXMsIGFzayBmb3IgdGhlbSBleHBsaWNpdGx5OgoKYGBge3J9CihkZiA8LSB0aWJibGUoeCA9IGMoMSwgTkEsIDMpKSkKYGBgCgpgYGB7cn0KZmlsdGVyKGRmLCB4ID4gMSkKYGBgCgpgYGB7cn0KZmlsdGVyKGRmLCBpcy5uYSh4KSB8IHggPiAxKQpgYGAKCi0tLQoKIyMgQXJyYW5nZSByb3dzIHdpdGggYGFycmFuZ2UoKWAKYGFycmFuZ2UoKWAgd29ya3Mgc2ltaWxhcmx5IHRvIGBmaWx0ZXIoKWAgZXhjZXB0IHRoYXQgaW5zdGVhZCBvZiBzZWxlY3Rpbmcgcm93cywgaXQgZG9lcyB3aGF0IGl0IGNsYWltcyBpdCBkb2VzOiBjaGFuZ2VzIHRoZWlyIG9yZGVyLiBJdCB0YWtlcyBhIGRhdGEgZnJhbWUgYW5kIGEgc2V0IG9mIGNvbHVtbiBuYW1lcyAob3IgbW9yZSBjb21wbGljYXRlZCBleHByZXNzaW9ucykgdG8gb3JkZXIgYnkuIElmIHlvdSBwcm92aWRlIG1vcmUgdGhhbiBvbmUgY29sdW1uIG5hbWUsIGVhY2ggYWRkaXRpb25hbCBjb2x1bW4gd2lsbCBiZSB1c2VkIHRvIGJyZWFrIHRpZXMgaW4gdGhlIHZhbHVlcyBvZiBwcmVjZWRpbmcgY29sdW1uczoKCmBgYHtyfQphcnJhbmdlKGZsaWdodHMsIHllYXIsIG1vbnRoLCBkYXkpCmBgYAoKVXNlIGBkZXNjKClgIHRvIHJlLW9yZGVyIGJ5IGEgY29sdW1uIGluIGRlc2NlbmRpbmcgb3JkZXI6CgpgYGB7cn0KYXJyYW5nZShmbGlnaHRzLCBkZXNjKGRlcF9kZWxheSkpCmBgYAoKTWlzc2luZyB2YWx1ZXMgYXJlIGFsd2F5cyBzb3J0ZWQgYXQgdGhlIGVuZDoKCmBgYHtyfQpkZiA8LSB0aWJibGUoeCA9IGMoNSwgNiwgMiwgTkEpKQphcnJhbmdlKGRmLCB4KQpgYGAKCmBgYHtyfQphcnJhbmdlKGRmLCBkZXNjKHgpKQpgYGAKCi0tLQoKIyMgU2VsZWN0IGNvbHVtbnMgd2l0aCBgc2VsZWN0KClgCkl0J3Mgbm90IHVuY29tbW9uIHRvIGdldCBkYXRhc2V0cyB3aXRoIGh1bmRyZWRzIG9yIGV2ZW4gdGhvdXNhbmRzIG9mIHZhcmlhYmxlcy4gSW4gdGhpcyBjYXNlLCB0aGUgZmlyc3QgY2hhbGxlbmdlIGlzIG9mdGVuIG5hcnJvd2luZyBpbiBvbiB0aGUgdmFyaWFibGVzIHlvdSdyZSBhY3R1YWxseSBpbnRlcmVzdGVkIGluLiBgc2VsZWN0KClgIGFsbG93cyB5b3UgdG8gZG8gdGhhdDoKCmBgYHtyfQojIFNlbGVjdCBjb2x1bW5zIGJ5IG5hbWUKc2VsZWN0KGZsaWdodHMsIHllYXIsIG1vbnRoLCBkYXkpCmBgYAoKYGBge3J9CiMgU2VsZWN0IGNvbHVtbnMgYmV0d2VlbiB5ZWFyIGFuZCBkYXkgKGluY2x1c2l2ZSkKc2VsZWN0KGZsaWdodHMsIHllYXI6ZGF5KQpgYGAKCmBgYHtyfQojIFNlbGVjdCBhbGwgY29sdW1ucyBleGNlcHQgdGhvc2UgZnJvbSB5ZWFyIHRvIGRheSAoaW5jbHVzaXZlKQpzZWxlY3QoZmxpZ2h0cywgLSh5ZWFyOmRheSkpCmBgYAoKVGhlcmUgYXJlIGEgbnVtYmVyIG9mIGhlbHBlciBmdW5jdGlvbnMgeW91IGNhbiB1c2Ugd2l0aGluIGBzZWxlY3QoKWA6CgoqIGBzdGFydHNfd2l0aCgiYWJjIilgOiBtYXRjaGVzIG5hbWVzIHRoYXQgYmVnaW4gd2l0aCAiYWJjIi4KKiBgZW5kc193aXRoKCJ4eXoiKWA6IG1hdGNoZXMgbmFtZXMgdGhhdCBlbmQgd2l0aCAieHl6Ii4KKiBgY29udGFpbnMoImlqayIpYDogbWF0Y2hlcyBuYW1lcyB0aGF0IGNvbnRhaW4gImlqayIuCiogYG51bV9yYW5nZSgieCIsIDE6MylgOiBtYXRjaGVzIGB4MWAsIGB4MmAgYW5kIGB4M2AuCiogYGV2ZXJ5dGhpbmcoKWA6IHVzZWZ1bCBmb3IgYnJpbmdpbmcgYSBmZXcgY29sdW1ucyB0byB0aGUgYmVnaW5uaW5nIG9mIHRoZSBkYXRhZnJhbWUgYW5kIHN0aWxsIHdhbnQgdG8ga2VlcCBldmVyeXRoaW5nIGVsc2UgYXQgdGhlIGVuZC4KClNlZSBgP3NlbGVjdGAgZm9yIG1vcmUgZGV0YWlscy4KCmBzZWxlY3QoKWAgY2FuIGJlIHVzZWQgdG8gcmVuYW1lIHZhcmlhYmxlcyAoZS5nLiwgYHNlbGVjdChmbGlnaHRzLCB5ID0geWVhcilgKSwgYnV0IGl0J3MgcmFyZWx5IHVzZWZ1bCBiZWNhdXNlIGl0IGRyb3BzIGFsbCBvZiB0aGUgdmFyaWFibGVzIG5vdCBleHBsaWNpdGx5IG1lbnRpb25lZC4gSW5zdGVhZCwgdXNlIGByZW5hbWUoKWAsIHdoaWNoIGlzIGEgdmFyaWFudCBvZiBgc2VsZWN0KClgIHRoYXQga2VlcHMgYWxsIHRoZSB2YXJpYWJsZXMgdGhhdCBhcmVuJ3QgZXhwbGljaXRseSBtZW50aW9uZWQ6CgpgYGB7cn0KcmVuYW1lKGZsaWdodHMsIHkgPSB5ZWFyLCB0YWlsX251bSA9IHRhaWxudW0pCmBgYAoKIyMjIyBgZXZlcnl0aGluZygpYCBleGFtcGxlCgpgYGB7cn0Kc2VsZWN0KGZsaWdodHMsIHRpbWVfaG91ciwgYXJpdGltZSA9IGFpcl90aW1lLCBldmVyeXRoaW5nKCkpCmBgYA==